package org.lestr.secuel.sogl.objets3D;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import org.lestr.secuel.m3ds.IUsine;
import org.lestr.secuel.m3ds.Variable;
import org.lestr.secuel.m3ds.animations.AnimationObjets3D;
import org.lestr.secuel.m3ds.maths.IPoint3d;
import org.lestr.secuel.m3ds.maths.IVecteur3d;
import org.lestr.secuel.m3ds.objets3D.IDimensionObjet3d;
import org.lestr.secuel.m3ds.objets3D.IObjet3d;
import org.lestr.secuel.m3ds.objets3D.IOrientationObjet3d;
import org.lestr.secuel.m3ds.objets3D.IPositionObjet3d;
import org.lestr.secuel.m3ds.objets3D.IApparence;
import org.lestr.secuel.m3ds.objets3D.ITriangle;
import org.lestr.secuel.sogl.Usine;
import org.lestr.secuel.sogl.maths.BoiteOrientee;
import org.lestr.secuel.sogl.maths.Transformation3D;

public class Objet3D implements IObjet3d {


    // Déclaration des attributs
    private IUsine usine;


    private String nom;


    private Collection<IEcouteur> écouteurs;


    private Collection<ITriangle> triangles;


    private Collection<Variable> variables;


    private IPositionObjet3d position;


    private IOrientationObjet3d orientation;


    private IDimensionObjet3d dimension;


    private IApparence texture;


    private BoiteOrientee boite;


    private boolean solide;


    private int transparence;


    private int réflexion;


    /** Constructeur */
    public Objet3D(IUsine usine) {

        // Instanciation des attributs
        this.usine = usine;
        nom = "Nouvel objet 3D";
        écouteurs = new ArrayList<IEcouteur>();
        triangles = new ArrayList<ITriangle>();
        variables = new ArrayList<Variable>();
        position = usine.positionObjet3d(this);
        orientation = usine.orientationObjet3d(this);
        dimension = usine.dimensionObjet3d(this);
        texture = usine.texture(this);
        boite = new BoiteOrientee();
        solide = true;
        transparence = 0;
        réflexion = 0;

    }// FIN Objet3D


    /* ----------------------------
     * Général
     * ---------------------------- */
    /** Défini le nom de l'objet */
    @Override
    public void définirNom(String nom) {

        this.nom = nom;

    }// FIN defNom


    /** Retourne le nom de l'objet */
    @Override
    public String obtenirNom() {

        return nom;

    }// FIN retNom


    /** Retourne les variables de l'objet */
    @Override
    public Collection<Variable> obtenirVariables() {

        return variables;

    }// FIN retVariables


    /** Ajoute un écouteur */
    @Override
    public Collection<IEcouteur> obtenirEcouteurs() {

        return écouteurs;

    }// FIN obtenirEcouteurs


    /* ----------------------------
     * Forme
     * ---------------------------- */
    /** Ajout des triangles constitués des points spécifiés à la forme */
    @Override
    public void ajouterTriangles(Collection<ITriangle> triangles) {

        // Ajout du triangle à la liste des triangles
        this.triangles.addAll(triangles);

    }// FIN ajouterTriangles


    /** Ajout d'un triangle constitué des points spécifiés à la forme */
    @Override
    public void ajouterTriangle(ITriangle triangle) {

        // Ajout du triangle à la liste des triangles
        triangles.add(triangle);

    }// FIN ajouterTriangle


    /** Retire un triangle de la forme */
    @Override
    public void retirerTriangle(ITriangle triangle) {

        // Retrait du triangle de la liste des triangles
        triangles.remove(triangle);

    }// FIN retirerTriangle


    /** Retourne les triangles de la forme */
    @Override
    public Iterable<ITriangle> obtenirTriangles() {

        return triangles;

    }// FIN retTriangles


    /* ----------------------------
     * Collisions
     * ---------------------------- */
    /** Ajoute un objet aux objet à prévenir en cas de collision */
    @Override
    public Collection<IEcouteurCollisions> obtenirEcouteursCollisions() {

        return null;

    }// FIN obtenirEcouteursCollisions


    /** Retourne les objets avec lesquels l'objet est en collision. Null si pas de collision */
    @Override
    public Iterator<IObjet3d> obtenirCollisions() {
        return null; // TODO
    }// FIN retCollisions


    /** Retourne vrai si l'objet est en collision avec l'objet spécifié */
    @Override
    public boolean estEnCollision(IObjet3d objet) {

        // Si l'objet n'est pas solide, retourne faux
        if (!solide)
            return false;

        // Si l'objet est un Objet3D
        if (objet instanceof Objet3D) {

            // Met à jour la boite de collision des objets
            actualiserBoite();
            ((Objet3D) objet).actualiserBoite();

            // Effectue le test
            return BoiteOrientee.estEnCollision(boite, ((Objet3D) objet).boite);// TODO PB

        }

        // Pas de collision avec les objets non-SOGL
        return false;

    }// FIN retEstEnCollision


    /** Met à jour la boite de collision de l'objet */
    private void actualiserBoite() {

        // Variables locales
        Transformation3D transformation = new Transformation3D();
        IVecteur3d axe;
        IPoint3d pointMinimum = usine.point3d(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY);
        IPoint3d pointMaximum = usine.point3d(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY);

        // Parcours les triangles pour récupérer le point minimum
        for (ITriangle triangle : triangles) {
            if (triangle.obtenirSommet1().obtenirX() < pointMinimum.obtenirX())
                pointMinimum.définirX(triangle.obtenirSommet1().obtenirX());
            if (triangle.obtenirSommet2().obtenirX() < pointMinimum.obtenirX())
                pointMinimum.définirX(triangle.obtenirSommet2().obtenirX());
            if (triangle.obtenirSommet3().obtenirX() < pointMinimum.obtenirX())
                pointMinimum.définirY(triangle.obtenirSommet3().obtenirX());
            if (triangle.obtenirSommet1().obtenirY() < pointMinimum.obtenirY())
                pointMinimum.définirY(triangle.obtenirSommet1().obtenirY());
            if (triangle.obtenirSommet2().obtenirY() < pointMinimum.obtenirY())
                pointMinimum.définirY(triangle.obtenirSommet2().obtenirY());
            if (triangle.obtenirSommet3().obtenirY() < pointMinimum.obtenirY())
                pointMinimum.définirY(triangle.obtenirSommet3().obtenirY());
            if (triangle.obtenirSommet1().obtenirZ() < pointMinimum.obtenirZ())
                pointMinimum.définirZ(triangle.obtenirSommet1().obtenirZ());
            if (triangle.obtenirSommet2().obtenirZ() < pointMinimum.obtenirZ())
                pointMinimum.définirZ(triangle.obtenirSommet2().obtenirZ());
            if (triangle.obtenirSommet3().obtenirZ() < pointMinimum.obtenirZ())
                pointMinimum.définirZ(triangle.obtenirSommet3().obtenirZ());
        }

        // Parcours les triangles pour récupérer le point maximum
        for (ITriangle triangle : triangles) {
            if (triangle.obtenirSommet1().obtenirX() > pointMaximum.obtenirX())
                pointMaximum.définirX(triangle.obtenirSommet1().obtenirX());
            if (triangle.obtenirSommet2().obtenirX() > pointMaximum.obtenirX())
                pointMaximum.définirX(triangle.obtenirSommet2().obtenirX());
            if (triangle.obtenirSommet3().obtenirX() > pointMaximum.obtenirX())
                pointMaximum.définirY(triangle.obtenirSommet3().obtenirX());
            if (triangle.obtenirSommet1().obtenirY() > pointMaximum.obtenirY())
                pointMaximum.définirY(triangle.obtenirSommet1().obtenirY());
            if (triangle.obtenirSommet2().obtenirY() > pointMaximum.obtenirY())
                pointMaximum.définirY(triangle.obtenirSommet2().obtenirY());
            if (triangle.obtenirSommet3().obtenirY() > pointMaximum.obtenirY())
                pointMaximum.définirY(triangle.obtenirSommet3().obtenirY());
            if (triangle.obtenirSommet1().obtenirZ() > pointMaximum.obtenirZ())
                pointMaximum.définirZ(triangle.obtenirSommet1().obtenirZ());
            if (triangle.obtenirSommet2().obtenirZ() > pointMaximum.obtenirZ())
                pointMaximum.définirZ(triangle.obtenirSommet2().obtenirZ());
            if (triangle.obtenirSommet3().obtenirZ() > pointMaximum.obtenirZ())
                pointMaximum.définirZ(triangle.obtenirSommet3().obtenirZ());
        }

        // Le point minimum est l'origine de la boite
        IVecteur3d origine = Usine.getSingleton().vecteur3D(pointMinimum.obtenirX(), pointMinimum.obtenirY(), pointMinimum.obtenirZ());
        transformation.setRotation(Usine.getSingleton().vecteur3D(1, 0, 0),
                                   Math.toRadians(obtenirOrientation().obtenirX()));
        transformation.transform(origine);
        transformation.setRotation(Usine.getSingleton().vecteur3D(0, 1, 0),
                                   Math.toRadians(obtenirOrientation().obtenirY()));
        transformation.transform(origine);
        transformation.setRotation(Usine.getSingleton().vecteur3D(0, 0, 1),
                                   Math.toRadians(obtenirOrientation().obtenirZ()));
        transformation.transform(origine);
        origine.définirX(obtenirPosition().obtenirX() + origine.obtenirX());
        origine.définirY(obtenirPosition().obtenirY() + origine.obtenirY());
        origine.définirZ(obtenirPosition().obtenirZ() + origine.obtenirZ());
        boite.setOrigine(origine);

        // Défini les distances
        boite.setLongueur(0, (pointMaximum.obtenirX() - pointMinimum.obtenirX()) * obtenirDimension().obtenirX());
        boite.setLongueur(1, (pointMaximum.obtenirY() - pointMinimum.obtenirY()) * obtenirDimension().obtenirY());
        boite.setLongueur(2, (pointMaximum.obtenirZ() - pointMinimum.obtenirZ()) * obtenirDimension().obtenirZ());

        // Calcul l'axe 1
        axe = Usine.getSingleton().vecteur3D(1, 0, 0);
        transformation.setRotation(Usine.getSingleton().vecteur3D(1, 0, 0),
                                   Math.toRadians(obtenirOrientation().obtenirX()));
        transformation.transform(origine);
        transformation.setRotation(Usine.getSingleton().vecteur3D(0, 1, 0),
                                   Math.toRadians(obtenirOrientation().obtenirY()));
        transformation.transform(origine);
        transformation.setRotation(Usine.getSingleton().vecteur3D(0, 0, 1),
                                   Math.toRadians(obtenirOrientation().obtenirZ()));
        transformation.transform(origine);
        boite.setAxe(0, axe);

        // Calcul l'axe 2
        axe = Usine.getSingleton().vecteur3D(0, 1, 0);
        transformation.setRotation(Usine.getSingleton().vecteur3D(1, 0, 0),
                                   Math.toRadians(obtenirOrientation().obtenirX()));
        transformation.transform(origine);
        transformation.setRotation(Usine.getSingleton().vecteur3D(0, 1, 0),
                                   Math.toRadians(obtenirOrientation().obtenirY()));
        transformation.transform(origine);
        transformation.setRotation(Usine.getSingleton().vecteur3D(0, 0, 1),
                                   Math.toRadians(obtenirOrientation().obtenirZ()));
        transformation.transform(origine);
        boite.setAxe(1, axe);

        // Calcul l'axe 3
        axe = Usine.getSingleton().vecteur3D(0, 0, 1);
        transformation.setRotation(Usine.getSingleton().vecteur3D(1, 0, 0),
                                   Math.toRadians(obtenirOrientation().obtenirX()));
        transformation.transform(origine);
        transformation.setRotation(Usine.getSingleton().vecteur3D(0, 1, 0),
                                   Math.toRadians(obtenirOrientation().obtenirY()));
        transformation.transform(origine);
        transformation.setRotation(Usine.getSingleton().vecteur3D(0, 0, 1),
                                   Math.toRadians(obtenirOrientation().obtenirZ()));
        transformation.transform(origine);
        boite.setAxe(2, axe);

    }// FIN actualiserBoite


    /* ----------------------------
     * Position
     * ---------------------------- */
    /** Défini la positionObjet3d X de l'objet */
    @Override
    public IPositionObjet3d obtenirPosition() {

        return position;

    }// FIN Méthode obtenirPosition


    /** Défini la positionObjet3d X de l'objet */
    @Override
    public IOrientationObjet3d obtenirOrientation() {

        return orientation;

    }// FIN Méthode obtenirOrientation


    /** Défini la positionObjet3d X de l'objet */
    @Override
    public IDimensionObjet3d obtenirDimension() {

        return dimension;

    }// FIN Méthode obtenirDimension


    /* ----------------------------
     * Apparence
     * ---------------------------- */
    /** Retourne la texture d'un objet3D */
    @Override
    public IApparence obtenirApparence() {

        return texture;

    }// FIN retTexture


    /** Défini la transparence d'un objet en pourcents */
    @Override
    public void définirTransparence(int taux) {

        // Si la transparence change vraiement
        if (transparence != taux) {

            // Mémorise
            int ancienneTransparence = transparence;
            transparence = taux;

            // Prévient les écouteurs
            for (IEcouteur écouteur : écouteurs)
                écouteur.transparenceChangée(this, ancienneTransparence);

        }

    }// FIN defTransparence


    /** Retourne la transparence d'un objet en pourcents */
    @Override
    public int obtenirTransparence() {

        return transparence;

    }// FIN retTransparence


    /** Défini la réflexion d'un objet en pourcents */
    @Override
    public void définirRéflexion(int taux) {

        // Si la réflexion change vraiment
        if (réflexion != taux) {

            // Mémorise
            int ancienneRéfexion = réflexion;
            réflexion = taux;

            // Prévient les écouteurs
            for (IEcouteur écouteur : écouteurs)
                écouteur.réflexionChangée(this, ancienneRéfexion);


        }

    }// FIN defRéflexion


    /** Retourne la réflexion d'un objet en pourcents */
    @Override
    public int obtenirRéflexion() {

        return réflexion;

    }// FIN retRéflexion


    /* ----------------------------
     * Animations
     * ---------------------------- */
    /** Ajoute une animation à l'objet */
    @Override
    public void ajoutAnimation(AnimationObjets3D animation) {
        // TODO
    }// FIN ajoutAnimation


    /** Retire une animation de l'objet */
    @Override
    public void retraitAnimation(AnimationObjets3D animation) {
        // TODO
    }// FIN retraitAnimation


    /** Retourne les animations de l'objet */
    @Override
    public Collection<AnimationObjets3D> getAnimations() {
        return null; // TODO
    }// FIN retAnimations


    /* ----------------------------
     * Animations propres
     * ---------------------------- */
    /** Lit l'animation interne d'un objet entre deux clés */
    @Override
    public void lire(int début,
                     int fin) {
        // TODO
    }// FIN lire


    /** Arrête la lecture de l'animation */
    @Override
    public void arrêter() {
        // TODO
    }// FIN arrêter


    /** Positionne l'objet à une certaine clé de l'animation interne */
    @Override
    public void setCléCourante(int clé) {
        // TODO
    }// FIN defCléCourante


    /** Retourne le nombre de clés de l'animation interne */
    @Override
    public int getNbClés() {
        return 0; // TODO
    }// FIN retNbClés


    /** Défini si l'animation doit être lue en boucle ou nom */
    @Override
    public void setEnBoucle(boolean enBoucle) {
        // TODO
    }// FIN defEnBoucle


    /** Retourne vrai si l'animation est lue en boucle */
    @Override
    public boolean isEnBoucle(boolean enBoucle) {
        return true; // TODO
    }// FIN isEnBoucle


}// FIN Classe
